/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.editor;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.change.provider.ChangeItemProviderAdapterFactory;
import org.eclipse.emf.ecore.provider.EcoreItemProviderAdapterFactory;
import org.eclipse.emf.ecore.xmi.UnresolvedReferenceException;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ComposeableAdapterFactory;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.parts.TreeViewer;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.uml2.uml.Profile;

import com.cea.papyrus.core.PapyrusMultiDiagramSharedObjects;
import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.actions.profile.DefineProfileAction;
import com.cea.papyrus.core.editor.abstracteditor.AbstractGraphicalPageWithFlyoutPalette;
import com.cea.papyrus.core.helper.IModelerHelper;
import com.cea.papyrus.core.helper.ModelerHelperRegistry;
import com.cea.papyrus.core.presentation.UMLExtendedItemProviderAdapterFactory;
import com.cea.papyrus.core.treeeditpart.CreateElementActionWithCommand;
import com.cea.papyrus.core.treeeditpart.TreeOutlinePage;
import com.cea.papyrus.core.ui.dialogs.PapyrusMessageDialog;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.provider.Di2ItemProviderAdapterFactory;
import com.cea.papyrus.multidiagram.extension.EditorFactoryRegistry;
import com.cea.papyrus.multidiagram.extension.ExtensionMultiDiagramEditor;
import com.cea.papyrus.multidiagram.extension.MultiDiagramSharedObjects;

/**
 * A GEF editor allowing multipages.
 * Each page is a GEF editor extending gef2.GraphicalPageWithFlyoutPalette
 */
public class MultiDiagramEditor extends ExtensionMultiDiagramEditor {

	/** Model manager */
	private ModelManager modelManager;

	/** The associated editing domain */
	protected DefaultEditDomain editDomain;

	/** boolean that indicates if the editor contents has changed or not */
	private Boolean toSave = false;

	/** link to the outline page */
	protected com.cea.papyrus.core.treeeditpart.TreeOutlinePage tlp;

	/** This is the one adapter factory used for providing views of the model */
	protected ComposedAdapterFactory adapterFactory;

	
	/** Listener on diagram added/removed. */
	private PropertyChangeListener modelManagerListener = new PropertyChangeListener() {
		public void propertyChange(PropertyChangeEvent evt) {
			String prop = evt.getPropertyName();
			if(ModelManager.UMLCHILD_ADDED_PROP.equals(prop)||
					ModelManager.UMLCHILD_MODIFIED_PROP.equals(prop)||
					ModelManager.UMLCHILD_REMOVED_PROP.equals(prop)||
					ModelManager.CHILD_MODIFIED_PROP.equals(prop)||
					ModelManager.CHILD_ADDED_PROP.equals(prop)||
					ModelManager.CHILD_REMOVED_PROP.equals(prop)){
				toSave=true;
				firePropertyChange(PROP_DIRTY);
			}
			if (ModelManager.CHILD_ADDED_PROP.equals(prop)
					|| ModelManager.CHILD_REMOVED_PROP.equals(prop)
					|| ModelManager.CHILD_MODIFIED_PROP.equals(prop)) {
				refreshTabs();
			}

			// specific test for diagram visibility => should open the page corresponding to the diagram
			if(ModelManager.CHILD_MODIFIED_PROP.equals(prop)) {
				if(evt.getNewValue() instanceof Diagram) {
					// open editor ?
					Diagram diagram = (Diagram)evt.getNewValue();
					MultiDiagramEditor.this.setActiveEditor(diagram);
				}
			}
			toSave=true;
			firePropertyChange(PROP_DIRTY);
		}
	};

	/**
	 * Create the shared object shared among nested editors.
	 */
	@Override
	protected MultiDiagramSharedObjects createSharedObject() {
		return new PapyrusMultiDiagramSharedObjects( getModelManager(), getEditDomain().getCommandStack(), getActionRegistry());
	}

	/**
	 * Return the EditorRegistry loading the extension points accepted by this editor.
	 * Should set the namespace.
	 */
	protected EditorFactoryRegistry createEditorRegistry() {
      return new EditorFactoryRegistry(com.cea.papyrus.core.PapyrusPlugin.PLUGIN_ID);
    }
	

	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.MultiPageEditorPart#isDirty()
	 */
	@Override
	public boolean isDirty() {
		return toSave;
	}

	/**
	 * Creates a new MultiDiagramEditor. 
	 * It creates the editing domain and the editor page factory
	 */
	public MultiDiagramEditor() {
		super();
		setEditDomain(new DefaultEditDomain(this));

		// init Adapter Factory
		List<ComposeableAdapterFactory> factories = new ArrayList<ComposeableAdapterFactory>();
		factories.add(new UMLExtendedItemProviderAdapterFactory());
		factories.add(new ResourceItemProviderAdapterFactory());
		factories.add(new Di2ItemProviderAdapterFactory());
		factories.add(new EcoreItemProviderAdapterFactory());
		factories.add(new ReflectiveItemProviderAdapterFactory());
		//darpan
		factories.add(new ChangeItemProviderAdapterFactory());

		adapterFactory = new ComposedAdapterFactory(factories);
		
		// creation of the associated modelmanager
		modelManager = new ModelManager(adapterFactory);
	}

	/**
	 * Sets the editing domain for the multipage editor.
	 * @param editDomain the new editing domain
	 */
	protected void setEditDomain(DefaultEditDomain editDomain) {
		this.editDomain = editDomain;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.MultiPageEditorPart#getActiveEditor()
	 */
	@Override
	public IEditorPart getActiveEditor() {
		// Overrides for method visibility
		return super.getActiveEditor();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.MultiPageEditorPart#setActivePage(int)
	 */
	@Override
	public void setActivePage(int pageIndex) {
		// Overrides for method visibility
		super.setActivePage(pageIndex);
	}

	/**
	 * Sets the site and input for this editor then creates and initializes the actions.
	 * Subclasses may extend this method, but should always call <code>super.init(site, input)
	 * </code>.
	 * @param site the site for which this part is being created; must not be <code>null</code>.
	 * @param input the input on which this editor should be created; must not be <code>null</code>.
	 * @throws PartInitException 
	 * @see org.eclipse.ui.IEditorPart#init(IEditorSite, IEditorInput)
	 */
	@Override
	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		try{
			if( input instanceof IFileEditorInput){
				super.init(site, input);
				// @TODO next one is already called from 
				// papyrus.multidiagram.extension.ExtensionMultiDiagramEditor.setInput(IEditorInput)
				// Should we call it anyway ?
				createActions();
			} else {
				PapyrusMessageDialog.display("you have to put your files into the workspace", null);
				this.dispose();
			}
		} catch(Exception e){
			Throwable exception=e.getCause();
			if (exception instanceof  UnresolvedReferenceException){
				PapyrusMessageDialog.display("You have a mistake in your file:\n "+ ((UnresolvedReferenceException)exception).getLocation()
						+" at the line :"+((UnresolvedReferenceException) exception).getLine()+"."
						+"\nThe error is "+exception.getLocalizedMessage(),e);
			} else {
				PapyrusMessageDialog.display("You have a mistake:\n"+exception, e);
			}
		}


	}

	/**
	 * Load the model from the provided input descriptor.
	 * @param input the input on which this editor should be created; must not be <code>null</code>.
	 * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
	 */
	@Override
	protected void setInput(IEditorInput input) {
		super.setInput(input);
		IFile file = ((IFileEditorInput) input).getFile();
		// Set editor name
		setPartName(file.getName());

		// Load resources
		modelManager.getDiResourceSet().loadResources(file);
		// modelManager = new ModelManager(resources);

	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public void doSave(IProgressMonitor monitor) {

		if(getModelManager().getTopPackageFromUMLResource() instanceof Profile){
			final Profile profileToDefine = (Profile)getModelManager().getTopPackageFromUMLResource();
			// open a question dialog to know if the user want to define the profile
			boolean response=MessageDialog.openQuestion(
					PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
					"Papyrus question", "In order to apply this profile, it had to be defined.\n" +
			"Would you like to define it?");
			
			// He do not want define so we leave
			if (response){
				DefineProfileAction action = (DefineProfileAction)getActionRegistry().getAction(DefineProfileAction.DEFINE_PROFILE_ACTION_ID );
				action.defineProfile(profileToDefine);
			}
		}
		toSave=false;
		super.firePropertyChange(PROP_DIRTY);
		try {
			// Save each associated resource
			modelManager.getDiResourceSet().saveResources(monitor);
			markSaveLocation();
		} catch (IOException ioe) {
			Debug.log(ioe);
		}
	}

	/**
	 * Mark the command stack of all sub-editors.
	 */
	protected void markSaveLocation() {
		for (int i=0; i<getPageCount(); i++) {
			IEditorPart editor = getEditor(i);
			if (editor instanceof AbstractGraphicalPageWithFlyoutPalette ) {
				AbstractGraphicalPageWithFlyoutPalette graphEditor = (AbstractGraphicalPageWithFlyoutPalette)editor;
				graphEditor.markSaveLocation();
			}
		}
	}

	/**
	 * Implements "Save As..." option
	 */
	@Override
	public void doSaveAs() {
		// Show a SaveAs dialog
		toSave=false;
		super.firePropertyChange(PROP_DIRTY);
		Shell shell = getSite().getWorkbenchWindow().getShell();
		SaveAsDialog dialog = new SaveAsDialog(shell);
		dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
		dialog.open();
		final IPath path = dialog.getResult();
		if (path != null) {
			// try to save the editor's contents under a different file name
			final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
			try {
				new ProgressMonitorDialog(shell).run(
						false, // don't fork
						false, // not cancelable
						new WorkspaceModifyOperation() { // run this operation
							@Override
							public void execute(final IProgressMonitor monitor) {
								try {
									modelManager.getDiResourceSet().saveAs(path);
								} catch (IOException ioe) {
									Debug.log(ioe);
								} 
							}
						});
				// set input to the new file
				setInput(new FileEditorInput(file));
				markSaveLocation();
			} catch (InterruptedException ie) {
				// should not happen, since the monitor dialog is not cancelable
				ie.printStackTrace(); 
			} catch (InvocationTargetException ite) { 
				ite.printStackTrace(); 
			}
		}
	}

	/**
	 * Editors should always be able to "save as"
	 * @return true
	 */
	@Override
	public boolean isSaveAsAllowed() {
		return true;
	}

	/**
	 * Returns the adapter for the specified key.
	 * <P><EM>IMPORTANT</EM> certain requests, such as the property sheet, may be made before
	 * or after {@link #createPartControl(Composite)} is called. The order is unspecified by
	 * the Workbench.
	 * @param type  the type of element to be adapted into.
	 * @return the new adapted object
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	@Override
	public Object getAdapter(Class type) {
		if (type == ActionRegistry.class) {
			//look the registry of the opened graphical editor
			IEditorPart part = getActiveEditor();
			if( part!= null) {
				return part.getAdapter(ActionRegistry.class);
			}
			return getActionRegistry();
		}
		// Add a viewer
		if (type == IContentOutlinePage.class){
			// return new com.cea.papyrus.core.common.treeeditpart.TreeOutlinePage(new TreeViewer(), getEditDomain(), (ActionRegistry)this.getAdapter(ActionRegistry.class), getSelectionSynchronizer(),modelManager);
			return new TreeOutlinePage(new TreeViewer(), getEditDomain(), (ActionRegistry)this.getAdapter(ActionRegistry.class), null, modelManager);
		}

		if(type == ModelManager.class) {
			return getModelManager();
		}

		if(type == EditingDomain.class) {
			return getModelManager().getDiResourceSet().getEditingDomain();
		}

		// get the command stack associated to this editor
		if (type == CommandStack.class){
			if(editDomain!=null)
				return editDomain.getCommandStack();
		}
		return super.getAdapter(type);
	}

	/**
	 * Returns the editing domain associated to this editor
	 * @return the editDomain
	 */
	protected DefaultEditDomain getEditDomain() {
		return editDomain;
	}

	/**
	 * Returns the Adapter Factory for this editor
	 * @return the Adapter Factory for this editor
	 */
	public AdapterFactory getAdapterFactory() {
		return adapterFactory;
	}
	
//	/**
//	 * Lazily creates and returns the action registry.
//	 * @return the action registry
//	 */
//	@Override
//	protected ActionRegistry getActionRegistry() {
//		if (actionRegistry == null) {
//			actionRegistry = new ActionRegistry();
//		}
//		return actionRegistry;
//	}
//
	/**
	 * Creates actions for this editor.  Subclasses should override this method to create
	 * and register actions with the {@link ActionRegistry}.
	 */
	protected void createActions() {
		// create CreateElementAction (one for each possible element in a model)
		super.createActions();
		ActionRegistry registry = getActionRegistry();
		createElementActions(registry);
		
		registry.registerAction(new DefineProfileAction(this));
		
	}

	/**
	 * Creates all CreateElementAction for the outline view associated to this editor.
	 * @param registry the action registry of the editor
	 */
	private void createElementActions(ActionRegistry registry) {
		// for each diagram, create "create actions"
		ModelerHelperRegistry factoryRegistry = PapyrusPlugin.getDefault().getFactoryRegistry();
		Iterator<IModelerHelper> it = factoryRegistry.getFactories().iterator();
		while(it.hasNext()) {
			IModelerHelper factory = it.next();
			Iterator<Class> metaclasses = factory.getDefinedMetaClasses().iterator();
			while(metaclasses.hasNext()) {
				Class metaclass = metaclasses.next();
				// if the action does not exist yet, creates a new one and add it to the repository
				if(registry.getAction(CreateElementActionWithCommand.getId(metaclass))== null) {
					registry.registerAction(new CreateElementActionWithCommand((IWorkbenchPart)this, metaclass));
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.editor.abstracteditor.AbstractMultiPageEditorPart#getModels()
	 */
	@Override
	public List getModels() {
		return modelManager.getVisibleDiagrams();
		//return currentModels;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.editor.abstracteditor.AbstractMultiPageEditorPart#activate()
	 */
	@Override
	public void activate() {
		super.activate();
		modelManager.getDiagramListener().addPropertyChangeListener(modelManagerListener);
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.editor.abstracteditor.AbstractMultiPageEditorPart#deactivate()
	 */
	@Override
	public void deactivate() {
		modelManager.getDiagramListener().removePropertyChangeListener(modelManagerListener);
		super.deactivate();
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.editor.abstracteditor.AbstractMultiPageEditorPart#dispose()
	 */
	@Override
	public void dispose() {
		getEditDomain().setActiveTool(null);
		modelManager = null;
		adapterFactory.dispose();
		super.dispose();
		try{
			this.finalize();
		}
		catch (Throwable e) {
			Debug.error(this, e.toString(), 0);
		}
	}

	/**
	 * Returns the model manager associated to this editor
	 * @return the modelManager
	 */
	public ModelManager getModelManager() {
		return modelManager;
	}
	
	/**
	 * Set the active page of this multi-page editor to the
	 * page that displays the specified model. This method has
	 * no effect of the given editor part is not contained in this
	 * multi-page editor.
	 * @param model the model displayed by the editor to be shown
	 */
	public void setActiveEditor(Object model) {
		IEditorPart modelPart = findEditor(model);
		if(modelPart != null) {
			setActiveEditor(modelPart);
		} 
	}

	
}
